#ifndef EPUBDUKPT_CRYPTO_TDES_HPP
#define EPUBDUKPT_CRYPTO_TDES_HPP

SJR_NAMESPACE_BEGIN

class EpubCryptoTdes final : public EpubCryptoBasic {
public:
  class ScheduleTdes : public Schedule {
  public:
    constexpr static int SIZE_DOUBLE = 16;
    constexpr static int SIZE_TRIPLE = 24;

    explicit ScheduleTdes(std::string const &in) : Schedule(), mKey() {
      auto &&fmtKey = EpubFormat::array(in);
      ScheduleTdes::keySet(mKey, fmtKey.data(), static_cast<int>(fmtKey.size()));
      this->setKey(mKey.data(), static_cast<int>(mKey.size()));
    }

    explicit ScheduleTdes(std::vector<unsigned char> const &in) : Schedule(), mKey() {
      ScheduleTdes::keySet(mKey, in.data(), static_cast<int>(in.size()));
      this->setKey(mKey.data(), static_cast<int>(mKey.size()));
    }

    ScheduleTdes(unsigned char const *in, int inLen) : Schedule(), mKey() {
      ScheduleTdes::keySet(mKey, in, inLen);
      this->setKey(mKey.data(), static_cast<int>(mKey.size()));
    }

  private:
    std::vector<unsigned char> mKey;

    static void keySet(std::vector<unsigned char> &local, unsigned char const *in, int inLen) {
      if (inLen > SIZE_DOUBLE)
        local.resize(SIZE_TRIPLE);
      else
        local.resize(SIZE_DOUBLE);

      auto &&autLen = local.size() > inLen ? inLen : local.size();
      memcpy(&local[0], in, autLen);
    }
  };

  auto blockSize(Schedule *key) -> int override {
    return 8;
  }

  auto encrypt(Schedule *key, unsigned char const *in, unsigned char *out) -> bool override {
    auto &&scheKey1 = DES_key_schedule();
    auto &&scheKey2 = DES_key_schedule();
    auto &&scheKey3 = DES_key_schedule();

    switch ((*key).size()) {
      case ScheduleTdes::SIZE_DOUBLE:
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[0]), &scheKey1);
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[8]), &scheKey2);
        DES_ecb2_encrypt(reinterpret_cast<DES_cblock *>(const_cast<unsigned char *>(in)),
          reinterpret_cast<DES_cblock *>(out), &scheKey1, &scheKey2, DES_ENCRYPT);
        return true;
      case ScheduleTdes::SIZE_TRIPLE:
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[0]), &scheKey1);
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[8]), &scheKey2);
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[16]), &scheKey3);
        DES_ecb3_encrypt(reinterpret_cast<DES_cblock *>(const_cast<unsigned char *>(in)),
          reinterpret_cast<DES_cblock *>(out), &scheKey1, &scheKey2, &scheKey3, DES_ENCRYPT);
        return true;
      default:
        return false;
    }
  }

  auto decrypt(Schedule *key, unsigned char const *in, unsigned char *out) -> bool override {
    auto &&scheKey1 = DES_key_schedule();
    auto &&scheKey2 = DES_key_schedule();
    auto &&scheKey3 = DES_key_schedule();

    switch ((*key).size()) {
      case ScheduleTdes::SIZE_DOUBLE:
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[0]), &scheKey1);
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[8]), &scheKey2);
        DES_ecb2_encrypt(reinterpret_cast<DES_cblock *>(const_cast<unsigned char *>(in)),
          reinterpret_cast<DES_cblock *>(out), &scheKey1, &scheKey2, DES_DECRYPT);
        return true;
      case ScheduleTdes::SIZE_TRIPLE:
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[0]), &scheKey1);
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[8]), &scheKey2);
        DES_set_key_unchecked(reinterpret_cast<DES_cblock *>(&(*key)[16]), &scheKey3);
        DES_ecb3_encrypt(reinterpret_cast<DES_cblock *>(const_cast<unsigned char *>(in)),
          reinterpret_cast<DES_cblock *>(out), &scheKey1, &scheKey2, &scheKey3, DES_DECRYPT);
        return true;
      default:
        return false;
    }
  }
};

SJR_NAMESPACE_END

#endif//EPUBDUKPT_CRYPTO_TDES_HPP
